<?php

// Namespace

namespace Loop;


/**
 * @package		Loop
 * @category	Factory
 */

class CObjectFactory
{
	/**
	 *
	 */
	
	public function api_export()
	{
		// Get API arguments

		$isExclusive = fv('isExclusive', false);
		$isIncremental = fv('isIncremental', false);
		$extensionIds = fv('extensionIds', false, null);
		$timezone = fv('timezone', true);

		
		// Call the method
		
		$this->export($timezone, $extensionIds, $isExclusive, $isIncremental);
	}
	
	
	/**
	 *
	 */
	
	public function api_import()
	{
		// Get API arguments

		$isExclusive = fv('isExclusive', false);
		$isIncremental = fv('isIncremental', false);
		$isStrict = fv('isStrict ', false, true);
		$extensionIds = fv('extensionIds', false, null);
		$timezone = fv('timezone', true);

		
		// Call the method
		
		$this->import($timezone, $extensionIds, $isExclusive, $isIncremental, $isStrict);
	}


	/**
	 * Build etl from object
	 *
	 * @param	object		$object 	The object
	 */

	private function buildEtl(&$object)
	{
		// Do we have an ETL available for this universe/object?

		$etlClassName = $object->className . '_Etl';
		$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($object->extensionId);
		$pathToEtl = $pathToExtension . 'Etl/' . $etlClassName . '.php';
		$pathToEtlUniverse = $pathToExtension . 'Etl/' . $etlClassName . '.' . pref('[Loop]/application/universe') . '.php';


		if (file_exists($pathToEtlUniverse) === true)
		{
			require_once($pathToEtlUniverse);

			return new $etlClassName();
		}
		else if (file_exists($pathToEtl) === true)
		{
			require_once($pathToEtl);

			return new $etlClassName();
		}
	}


	/**
	 * Build  object
	 *
	 * @param	string		$extensionId 	The extension id
	 * @param	string		$className 		The class name
	 */

	public function buildObject($extensionId, $className)
	{
		// Does the model exist?

		$pathToModel = app()->getPathHelper()->convertExtensionIdToPath($extensionId) . 'Model/' . $className . '.php';

		if (file_exists($pathToModel) === false)
		{
			e
			(
				EXCEPTION_OBJECT_NOT_DEFINED,
				array
				(
					'className' => $className,
					'extensionId' => $extensionId,
					'pathToModel' => $pathToModel
				)
			);
		}

		
		// Create the object

		require_once($pathToModel);


		$name = $className;

		if (class_exists($name) === false)
		{
			$name = app()->getPathHelper()->convertExtensionIdToNamespace($extensionId) . $name;
		}

		$object = new $name();


		return $object;
	}


	/**
	 * Build  path to file csv
	 *
	 * @param	object		$object 		The object
	 * @param	string		$direction 		Import/Export
	 */

	public function buildPathToCsv($object, $direction)
	{
		$pathToCsv = path
		(
			array
			(
				PATH_APPLICATION,
				'Data',
				$direction,
				$object->extensionId
			),
			true
		);

		$pathToCsv .= $object->objectName . '.csv';


		return $pathToCsv;
	}


	/**
	 * Checks the value of the property, triggers an exception if not valid
	 *
	 * @param	object		$object 		The object	 
	 * @param	string		$propertyName	The name of the property
	 * @param	array		$property		The array that contains everything about the property
	 * @param	mixed		$propertyValue	The potential new value of the property
	 */
	
	public function checkPropertyValue(&$object, $propertyName, &$property, $propertyValue)
	{
		// We don't check smart properties
		
		if ($property['isSmart'] == true)
		{
			return;
		}
		
		
		// Make sure we're working on a string
		
		$propertyValue = strval($propertyValue);
		
		
		// If the value is empty
		
		if (strlen($propertyValue) == 0)
		{
			if 
			(
				($property['isRequired'] == true) &&
				($property['isLocalized'] == false)
			)
			{
				// Value empty, yet required, so no good!
				
				e
				(
					EXCEPTION_OBJECT_PROPERTY_VALUE_REQUIRED,
					array
					(
						'className' => $object->className,
						'propertyName' => $propertyName,
						'propertyPattern' => $property['pattern'],
						'propertyValue' => $propertyValue
					)
				);
			}
			else
			{
				// Value empty, not required, we can stop checking
				
				return;
			}
		}
		
		
		// Make sure we use a string value
		
		$propertyValue = strval($propertyValue);
		
		
		// Check if the syntax of the value is valid
		
		if
		(
			(empty($property['pattern']) === false) &&
			(preg_match($property['pattern'], $propertyValue) == false)
		)
		{
			// Otherwise, no good!
			
			e
			(
				EXCEPTION_OBJECT_PROPERTY_VALUE_NOT_VALID,
				array
				(
					'className' => $object->className,
					'propertyName' => $propertyName,
					'propertyPattern' => $property['pattern'],
					'propertyValue' => $propertyValue
				)
			);
		}
	}

	
	/**
	 * Check special options
	 *
	 * @param	object		$object 		The object
	 * @param	array		$options 		The options
	 */


	public function checkSpecialOptions(&$object, &$options)
	{
		// Should we select disabled objects too?
		
		if ($options['acceptDisabledObjects'] == false)
		{
			$options['internalConditions']['timeOfDisabling'] = array(EQUAL, IS_NULL);
		}
		
		
		// Should we select deleted objects too?
		
		if ($options['acceptTemporaryObjects'] == false)
		{
			$options['internalConditions']['isTemporary'] = array
			(
				LOOP_OR,
				array
				(
					array(EQUAL, IS_NULL),
					array(EQUAL, false)
				)
			);
		}
		
		
		// Take care of language if not defined (for localized objects only)
		// A valid language condition must contain an array of 2 elements, e.g. EQUAL 'en'
		
		if
		(
			($object->isLocalized == true) &&
			(isset($options['internalConditions']['languageIsoCode']) == false) && 
			(
				(isset($options['conditions']['languageIsoCode']) == false) ||
				(count($options['conditions']['languageIsoCode']) != 2) ||
				(is_null($options['conditions']['languageIsoCode'][1]) == true)
			)
		)
		{
			$options['internalConditions']['languageIsoCode'] = array(IN, '(\'' . implode('\', \'', pref('[Loop]/localization/languages')) . '\')');
		}
	}


	/**
	 * Clean table if exists
	 *
	 * @param 	object 	$object 	The object to clean
	 */
	
	public function clean(&$object)
	{		
		// Remove the content of the appropriate table

		$queryVariables = array
		(
			$object->databaseName,
			$object->tableName
		);

		$checkTableExistSql = 'SHOW TABLES LIKE \'%2\'';

		$result = $object->databaseDriver->executeQuery($checkTableExistSql, $queryVariables);

		if ($result->num_rows > 0)
		{
			$query = 'TRUNCATE TABLE `%1`.`%2`';
			
			$object->databaseDriver->executeQuery($query, $queryVariables);
		}
	}


	/**
	 * Converts a property to a number of relations
	 *
	 * @param 	object 	$object 		The object
	 * @param	string	$propertyName	The name of the property to get
	 *
	 * @return	int						The number of relations
	 */
	
	public function convertPropertyToNbObjects(&$object, $propertyName)
	{
		// Get the property
		
		$property = $object->getProperty($propertyName);


		// Create the relation
		
		$relation = $this->convertPropertyToRelation($object, $propertyName);
		
		
		// How's the condition going to be called?
		
		if (empty($property['propertyName']) == true)
		{
			$conditionName = $object->idName;
		} 
		else
		{
			$conditionName = $property['propertyName'];
		}
		
		
		// Count the number of relations
		
		$nbRelations = $relation->count
		(
			array
			(
				'conditions' => array
				(
					$conditionName => array(EQUAL, $object->getId())
				)
			)
		);
		
		
		return $nbRelations;
	}

	
	/**
	 * Converts a property to an object
	 *
	 * @param 	object 	$object 			The object
	 * @param	string	$propertyName		The name of the property to get
	 * @param	array	$objectProperties	The list of object properties that should be loaded
	 *
	 * @return	object						The object
	 */
	
	public function convertPropertyToObject(&$object, $propertyName, $objectProperties = null)
	{			
		// Find the property and return it
		
		$property = null;
		
		if (isset($object->staticProperties[$propertyName]) == true)
		{
			$property = &$object->staticProperties[$propertyName];
		}
		else if (isset($object->localizedProperties[$propertyName]) == true)
		{
			$property = &$object->localizedProperties[$propertyName];
		}
		else if (isset($object->fileProperties[$propertyName]) == true)
		{

			$property = &$object->fileProperties[$propertyName];
		}
		else if (isset($object->smartProperties[$propertyName]) == true)
		{
			$property = &$object->smartProperties[$propertyName];
		}
		
		
		// Have we found the property?
		
		if (is_array($property) == false)
		{
			e
			(
				EXCEPTION_OBJECT_PROPERTY_NOT_FOUND,
				array
				(
					'className' => $object->className,
					'propertyName' => $propertyName
				)
			);
		}
		
		
		// Do we already have an object?
		
		if 
		(
			(is_object($property['object']) == true) &&
			($property['object']->getId() == $property['value'])
		)
		{
			$object = &$property['object'];
			
			return $object;
		}
		
		
		// Can we convert it to an object?
		
		$pathsToExtensions = app()->getExtensionManager()->getPathsToExtensions();

		foreach ($pathsToExtensions as $pathToExtension)
		{
			// Does the model exist?
			
			$pathToModel = $pathToExtension . 'Model/' . $property['className'] . '.php';
			
			if (file_exists($pathToModel) == false)
			{
				continue;
			}
			
			// Good, create the object
			
			require_once($pathToModel);
			

			// Is there a namespace ?

			$className = $property['className'];
			$className = '\\' . str_replace('-', '\\', $property['extensionId']) . '\\' . $property['className'];

			if (class_exists($className) === false)
			{
				continue;
			}

			$object = new $className();
			
			
			// Load the object
			
			$object->load($property['value'], $objectProperties);
			
			
			// Store the object in the property
			
			$property['object'] = &$object;
			
			
			// Return the object
			
			return $object;
		}
		
		
		// Ouch, if we're there we have no object to return
		
		e
		(
			EXCEPTION_OBJECT_CONVERT_PROPERTY_TO_OBJECT_FAILED,
			array
			(
				'className' => $object->className,
				'propertyName' => $propertyName,
				'propertyValue' => $property['value']
			)
		);
	}


	/**
	 * Converts a property to a list of objects
	 *
	 * @param 	object 	$object 			The object
	 * @param	string	$propertyName		The name of the property to get
	 * @param	array	$objectProperties	The list of object properties that should be loaded
	 *
	 * @return	array						The objects
	 */
	
	public function convertPropertyToObjects(&$object, $propertyName, $objectProperties = null)
	{
		// Get the property

		$property = $object->getProperty($propertyName);


		// First, convert the property into raw objects
		
		$rawObjects = $this->convertPropertyToRawObjects($object, $propertyName, $objectProperties);
	

		// We'll need this model

		require_once($property['pathToExtension'] . 'Model/' . $property['className'] . '.php');

		
		// We can now load each raw object into an object
		
		$objects = array();
		
		foreach ($rawObjects as $rawObject)
		{
			// Is there a namespace

			$className = $property['className'];

			if (class_exists($property['className']) === false)
			{
				$className  = '\\' . str_replace('-', '\\', $property['extensionId']) . '\\' . $property['className'];
			}


			// Create a new object
	
			$newObject = new $className();


			// Load it
			
			$newObject->loadFromRawObject($rawObject);
			

			// Add it to the list of final objects
			
			$objects[$newObject->getId()] = $newObject;
		}
		

		return $objects;
	}


	/**
	 * Converts a property to a set of raw objects
	 *
	 * @param 	object 	$object 			The object
	 * @param	string	$propertyName		The name of the property to get
	 * @param	array	$objectProperties	The list of object properties that should be loaded
	 *
	 * @return	array						An array of raw objects
	 */
	
	public function convertPropertyToRawObjects(&$object, $propertyName, $objectProperties = null)
	{
		// Get the property
		
		$property = $object->getProperty($propertyName);


		// Get raw relations
		
		$result = $this->convertPropertyToRawRelations($object, $propertyName, $objectProperties, $property['propertyName']);
		
		
		// If it's a direction relation, we've got our objects!

		if ($property['isDirect'] == true)
		{
			return $result['rawRelations'];
		}


		// Otherwise get the list of objects IDs
		
		$pathToModel = 	$property['pathToExtension'] . 'Model/' . $property['className'] . '.php';

		if (file_exists($pathToModel) === false)
		{
			e
			(
				EXCEPTION_OBJECT_NOT_DEFINED,
				array
				(
					'pathToModel' => $pathToModel,
					'property' => print_r($property, true),
					'propertyName' => $propertyName
				)
			);
		}
		
		require_once($pathToModel);


		// Is there a namespace ?

		$className = $property['className'];

		if (class_exists($property['className']) === false)
		{
			$className = str_replace('-', '\\', $property['extensionId']) . '\\' . $property['className'];
		}

		$newObject = new $className();
		$objectsIds = array();

		foreach ($result['rawRelations'] as $rawRelation)
		{
			$result['relation']->loadFromRawObject($rawRelation);
			$objectsIds[] = $result['relation']->getPropertyValue($newObject->idName);
		}
		
		
		// If no object ID was found, give up
		
		if (count($objectsIds) == 0)
		{
			return array();
		}
		
		
		// Get objects
		
		$rawObjects = $newObject->find
		(
			array
			(
				'conditions' => array
				(
					'id' => array(IN, $objectsIds)
				)
			)
		);

		
		return $rawObjects;
	}


	/**
	 * Converts a property to a set of raw relations
	 *
	 * @param 	object  $object 			The object
	 * @param	string	$propertyName		The name of the property to get
	 * @param	array	$objectProperties	The list of object properties that should be loaded
	 * @param	string	$parentPropertyName	Special propertyName for a relation
	 *
	 * @return	array						An array containing the relation object and raw relations found
	 */
	
	public function convertPropertyToRawRelations(&$object, $propertyName, $objectProperties = null, $parentPropertyName = null)
	{
		// Create the relation
		
		$relation = $this->convertPropertyToRelation($object, $propertyName);
		
		
		// How's the condition going to be called?
		
		if (empty($parentPropertyName) == true)
		{
			$conditionName = $object->idName;
		} 
		else
		{
			$conditionName = $parentPropertyName;
		}
		
		
		// Find all relations associated with this object
		
		$rawRelations = $relation->find
		(
			array
			(
				'conditions' => array
				(
					$conditionName => array(EQUAL, $object->getId())
				),
				'properties' => $objectProperties
			)
		);
		
		
		// Prepare result
		
		$result = array
		(
			'relation' => $relation,
			'rawRelations' => $rawRelations
		);
		
		
		return $result;
	}


	/**
	 * Converts a property to a relation
	 *
	 * @param 	object 	$object 		The object
	 * @param	string 	$propertyName 	The name of the property to convert
	 *
	 * @return	object					The relation
	 */
	
	public function convertPropertyToRelation(&$object, $propertyName)
	{
		// Get the property
		
		$property = $object->getProperty($propertyName);

		
		// Property must be a composition, an aggregation or an association
		
		if
		(
			($property['isAggregation'] == false) &&
			($property['isAssociation'] == false) &&
			($property['isComposition'] == false)
		)
		{
			e
			(
				EXCEPTION_OBJECT_CONVERT_PROPERTY_TO_RAW_OBJECTS_FAILED,
				array
				(
					'className' => $object->className,
					'propertyName' => $propertyName
				)
			);
		}
		
		
		// Is the relation direct or not?
		
		if
		(
			($property['isAssociation'] == true) ||
			($property['isDirect'] == true)
		)
		{
			$relationName = $property['className'];
		}
		else
		{
			// We need to know the name of the relation class
			
			if (empty($property['relationName']) == true)
			{
				$classNames = array($property['className'], $object->classNameWithoutNamespace);

				sort($classNames, SORT_STRING);
				
				$relationName = implode('_', $classNames);
			}
			else
			{
				$relationName = $property['relationName'];
			}
		}

		
		// Do we have a model?
		
		$pathToModel = $property['pathToExtension'] . 'Model/' . $relationName . '.php';

		if (file_exists($pathToModel) == false)
		{
			e
			(
				EXCEPTION_OBJECT_NOT_DEFINED,
				array
				(
					'className' => $relationName,
					'pathToModel' => $pathToModel
				)
			);
		}
		
		
		// Good, let's create it
		
		require_once($pathToModel);
		

		// Is there a namespace

		$relationObject = $relationName;

		if (class_exists($relationName) === false) 
		{
			$relationObject = str_replace('-', '\\',  $property['extensionId']) . '\\' . $relationName;
		}

		$relation = new $relationObject();
		
		
		return $relation;
	}


	/**
	 * Converts an object to a raw object
	 *
	 * @param 	object 	$object 	The object
	 *
	 * @return	array	An associative array, i.e. the object in raw form
	 */
			
	public function convertToRawObject(&$object)
	{
		$rawObject = array();
		
		foreach ($object->staticProperties as $propertyName => $propertyOptions)
		{
			$rawObject[$propertyName] = $propertyOptions['value'];
		}

		foreach ($object->localizedProperties as $propertyName => $propertyOptions)
		{
			$rawObject[$propertyName] = $propertyOptions['value'];
		}

		return $rawObject;
	}



	/**
	 * Counts the total number of objects
	 *
	 * @param 	object 	$object 	The object	 
	 * @param	array	$options	An array containing options
	 *
	 * @return	int					The number of objects found
	 */
	
	public function count(&$object, $options = null)
	{
		// Make sure $options is an array
		
		if (is_array($options) == false)
		{
			$options = array();
		}
		

		// Properties of options will always be a COUNT
		
		$options['smartProperties'] = array('COUNT(DISTINCT `%1`.`%2`.`id`) AS `nbObjects`');


		// Perform a simple find to count objects
		
		$rawResults = $object->find($options);
		
		// Get the first result
		
		$rawResult = array_pop($rawResults);
		
		
		// Get the number of objects
		
		$nbObjects = $rawResult['nbObjects'];
		
		
		return $nbObjects;
	}



	/**
	 * Create core properties
	 *
	 * @param 	object 	$object 	The object	 
	 */

	public function createCoreProperties(&$object)
	{
		// Create non-localized properties
		
		$object->createProperty
		(
			'id',
			array
			(
				'isCore' => true,
				'isIncremental' => true,
				'isPrimaryKey' => true,
				'type' => TYPE_ID
			)
		);
		
		$object->createProperty
		(
			'reference',
			array
			(
				'isCore' => true,
				'type' => TYPE_REFERENCE
			)
		);

		$object->createProperty
		(
			'externalId',
			array
			(
				'isCore' => true,
				'type' => TYPE_STRING_LONG
			)
		);
		
		$object->createProperty
		(
			'externalSource',
			array
			(
				'isCore' => true,
				'type' => TYPE_STRING_LONG
			)
		);

		$object->createProperty
		(
			'externalTimeOfCreation',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'isTemporary',
			array
			(
				'isCore' => true,
				'type' => TYPE_BOOLEAN
			)
		);

		$object->createProperty
		(
			'sourceOfCreation',
			array
			(
				'isCore' => true,
				'type' => TYPE_STRING_MEDIUM
			)
		);
		
		$object->createProperty
		(
			'sourceOfDeletion',
			array
			(
				'isCore' => true,
				'type' => TYPE_STRING_MEDIUM
			)
		);
		
		$object->createProperty
		(
			'sourceOfUpdate',
			array
			(
				'isCore' => true,
				'type' => TYPE_STRING_MEDIUM
			)
		);
		
		$object->createProperty
		(
			'timeOfCreation',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'timeOfDeletion',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'timeOfDisabling',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'timeOfLockage',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'timeOfUpdate',
			array
			(
				'isCore' => true,
				'type' => TYPE_TIMESTAMP
			)
		);
		
		$object->createProperty
		(
			'userCreatorId',
			array
			(
				'className' => 'CUser',
				'extensionId' => EXTENSION_APPLICATION,
				'isAssociation' => true,
				'isCore' => true
			)
		);
		
		$object->createProperty
		(
			'userDeleterId',
			array
			(
				'className' => 'CUser',
				'extensionId' => EXTENSION_APPLICATION,
				'isAssociation' => true,
				'isCore' => true
			)
		);
		
		$object->createProperty
		(
			'userDisablerId',
			array
			(
				'className' => 'CUser',
				'extensionId' => EXTENSION_APPLICATION,
				'isAssociation' => true,
				'isCore' => true
			)
		);
		
		$object->createProperty
		(
			'userLockerId',
			array
			(
				'className' => 'CUser',
				'extensionId' => EXTENSION_APPLICATION,
				'isAssociation' => true,
				'isCore' => true
			)
		);
		
		$object->createProperty
		(
			'userUpdaterId',
			array
			(
				'className' => 'CUser',
				'extensionId' => EXTENSION_APPLICATION,
				'isAssociation' => true,
				'isCore' => true
			)
		);

		
		// Create properties dedicated to localization
		
		$object->createProperty
		(
			'languageIsoCode',
			array
			(
				'isCore' => true,
				'isLocalized' => true,
				'isPrimaryKey' => true,
				'type' => TYPE_LANGUAGE_ISO_CODE
			)
		);
		
		$object->createProperty
		(
			$object->idName,
			array
			(
				'isCore' => true,
				'isLocalized' => true,
				'isPrimaryKey' => true,
				'type' => TYPE_ID
			)
		);


		// Create indexes

		$this->createIndex($object, array('id', 'reference'));
		$this->createIndex($object, array('externalSource', 'externalId'));
	}


	/**
	 * Create custom propertoes
	 *
	 * @param 	object 	$object 	The object	 
	 */

	public function createCustomProperties(&$object)
	{
		// Any custom properties for this object?

		$customProperties = pref('[' . $object->extensionId . ']/model/' . $object->className . '/customProperties');


		// Create custom properties

		$nbCustomProperties = pref('[Loop]/model/nbCustomProperties');
		
		for ($i = 0; $i < $nbCustomProperties; $i++)
		{
			// Define the type of the property

			$propertyType = TYPE_STRING_MEDIUM;

			if (isset($customProperties[$i]) === true)
			{
				$propertyType = $customProperties[$i]['type'];
			}


			// Create the property
			
			$object->createProperty
			(
				'c' . $i,
				array
				(
					'type' => $propertyType,
					'isCustom' => true
				)
			);
		}
	}


	/**
	 * Create database
	 *
	 * @param 	string 	$extensionId 	The extensionId	 
	 * @param 	string 	$databaseName 	The name of the database		 
	 */

	public function createDatabase($extensionId, $databaseName = null)
	{
		if (pref('[' . $extensionId . ']/database/isShared') == true)
		{
			return;
		}

		
		// Connect with loop parameters

		$databaseDriver = db($extensionId);

		$databaseDriver->connect(pref('[Loop]/database/host'), pref('[Loop]/database/root/login'),  pref('[Loop]/database/root/password'));


		// Define the database name
			
		if (is_null($databaseName) === true)
		{
			$databaseName = pref('[' . $extensionId . ']/database/name');
		}


		// Create the database
				
		$query = 'CREATE DATABASE IF NOT EXISTS `%1`';

		$queryVariables = array
		(
			$databaseName,
		);
			
		$databaseDriver->executeQuery($query, $queryVariables);
	}


	/**
	 * Creates a feature
	 *
	 * @param 	object 	$object 		The object		 
	 * @param	string	$extensionId	The name of extension
	 * @param	string	$featureName	The name of feature needed
	 */

	public function createFeature(&$object, $extensionId, $featureName)
	{
		// Declare the extension if needed

		if (isset($object->features[$extensionId]) == false)
		{
			$object->features[$extensionId] = array();
		}


		// Declare the feature, if needed

		if (in_array($featureName, $object->features[$extensionId]) == false)
		{
			$object->features[$extensionId][] = $featureName;
		}


		// Initialize the feature for this object

		app()->getFeatureManager()->initializeFeatureOnObject($extensionId, $featureName, $object);
	}


	/**
	 * Creates a filter
	 *
	 * @param 	object 	$object 					The object		 	 
	 * @param	string	$filterName					The name of the filter
	 * @param	string	$filterProperties			The properties of the filter
	 * @param	string	$filterRelationalOperator	The relational operator associated with the filter
	 * @param	boolean	$isKey						True to enable the filter from start, false otherwise
	 * @param	boolean	$isHidden					True to hide from end-user, false otherwise
	 */
	
	public function createFilter(&$object, $filterProperties, $filterRelationalOperator, $isKey = false, $isHidden = false)
	{
		// Store the filter
		
		$object->filters[$filterProperties] = array
		(
			'relationalOperator' => $filterRelationalOperator,
			'isKey' => $isKey,
			'isHidden' => $isHidden
		);

		$this->createIndex($object, array($filterProperties));
	}


	/**
	 * Filled an array which will serve to create indexes at the time of forge
	 * 
	 * @param 	object 	$object 			The object			 
	 * @param 	array 	$indexedProperties 	Contains columns concerned by the creation of the index
	 */
	
	public function createIndex(&$object, $indexedProperties = array())
	{
		// Get back the type of the first property to compare with the 
		// others and verify that they are all of the same type (localized | static)
		
		$property = $object->getProperty($indexedProperties[0]);
		$type = $property['isLocalized'];
		

		// Number to make a fast buckle
		
		$nbIndexedProperties = count($indexedProperties);
		

		// Read the indexed properties array
		
		for($i = 0; $i < $nbIndexedProperties; $i++)
		{
			// Get the type of the property (localized | static)
			
			$property = $object->getProperty($indexedProperties[$i]);
		    

			// If the type of the current property is different of the first property, it's necessary 
			// to call the proxy function e() because it's forbidden to create an property group indexed on two tables
			
			if ($property['isLocalized'] != $type)
			{
				e
				(
					EXCEPTION_VARIABLE_CONTENT_LOCALIZED_AND_STATICS_PROPERTIES,
					array
					(
						'class' => $object->className,
						'extensionId' => $object->extensionId,
						'variableName' => 'indexedProperties'
					)
				);
			}
			
			$type = $property['isLocalized'];	
		}
		

		// Define the table name of the properties group
		
		$index = ($type == true) ? $object->tableName_L : $object->tableName;


		// Fill the indexes array
		
		$object->indexes[$index] = (isset($object->indexes[$index]) == false) ? array() : $object->indexes[$index];	
		$object->indexes[$index][] = $indexedProperties;
	}


	/**
	 * Create the indexes of the table
	 * 
	 * @param 	object 	$object 	The objec	 
	 * @param 	string	$tableName	The table name
	 * @param 	array				$properties	The properties indexed by the table
	 */
	
	public function createIndexesTemplate(&$object, $tableName, $properties)
	{
		$indexes = array();

		// Read the indexed properties
		
		foreach($properties as $columns)
		{
			// Create the index's name
			
			$indexName = 'index_'.implode('_', $columns);

			// If the index isn't still create 

			if(!in_array($indexName, $indexes) == true)
			{
				$queryVariables = array($tableName);
				
				// Create the sql query
		
				$query = 'CREATE INDEX `%1` ON `%2`('.implode(', ', $columns).')';
				
				// Add the name of the table and the index name at the properties
				
				$queryVariables = array($indexName, $object->databaseName . '`.`' . $tableName);
				
				// Exec sql query
				
				$object->databaseDriver->executeQuery($query, $queryVariables);


				// Storage the created indexes

				$indexes[] = $indexName;
			}
		}
	}


	/**
	 * Creates table(s) required to store the object
	 *
	 * @param 	object 	$object 			The object	 
	 */
	
	public function createTable(&$object)
	{
		// Create non-localized table

		$this->createTableTemplate($object, false);
		

		// Create localized table

		if ($object->isLocalized == true)
		{
			$this->createTableTemplate($object, true);
		}
	}
	

	/**
	 * Template function to create the database table required to store the object
	 *
	 * @param 	object 	$object 			The object		 
	 * @param	bool	$isLocalized	True to create the _L table, false for the main one
	 */
	
	protected function createTableTemplate(&$object, $isLocalized = false, $customFields = null)
	{
		// Should we work on static or localized properties?
		
		if ($isLocalized == true)
		{
			$properties = $object->localizedProperties;
			$tableName = $object->tableName_L;
		}
		else
		{
			$properties = $object->staticProperties;
			$tableName = $object->tableName;
		}	
		

		// Delete the table
		
		$query = 'DROP TABLE IF EXISTS `%1`.`%2`';

		$queryVariables = array
		(
			$object->databaseName,
			$tableName
		);

		$object->databaseDriver->executeQuery($query, $queryVariables);


		// Parse all properties to write the corresponding SQL instruction
		
		$fields = array();
		$primaryKeys = array();
		$uniqueProperties = array();

		foreach ($properties as $propertyName => $propertyOptions)
		{
			// Format additional settings and primary keys
			
			$options = array();
			
			if ($propertyOptions['isIncremental'] == true)
			{
				$options[] = 'AUTO_INCREMENT';
			}
			
			if ($propertyOptions['isPrimaryKey'] == true)
			{
				$options[] = 'NOT NULL';
				$primaryKeys[] = '`' . $propertyName . '`';
			}
			else if ($propertyOptions['defaultValue'] === null)
			{
				$options[] = 'DEFAULT NULL';
			}
			else
			{
				$options[] = 'DEFAULT \'' . $propertyOptions['defaultValue'] . '\'';
			}
			
			if ($propertyOptions['isUnique'] == true)
			{
				$uniqueProperties[] = $propertyName;
			}
			
			
			// Add the field

			$fields[] = '`' . $propertyName . '` ' . $propertyOptions['sqlType'] . ' ' . implode(' ', $options);
		}


		// Inject custom fields, if any

		if (is_array($customFields) === true)
		{
			$fields = array_merge($fields, $customFields);
		}
		
		
		// Do we have unique constraints?
		
		$queryUnique = '';
		
		if (count($uniqueProperties) > 0)
		{
			foreach ($uniqueProperties as $propertyName)
			{
				$queryUnique .= ', CONSTRAINT `unique_' . $propertyName . '` UNIQUE (`' . $propertyName . '`) ';
			}
		}
		
		
		// Create the main table
		
		$query = 'CREATE TABLE `%1`.`%2`(' . implode(', ', $fields) . ', PRIMARY KEY (' . implode(', ', $primaryKeys) . ')' . $queryUnique . ') ENGINE=%3 DEFAULT CHARSET=utf8';

		$queryVariables = array($object->databaseName, $tableName, $object->engine);
		

		$object->databaseDriver->executeQuery($query, $queryVariables);
		
		
		// Call the createIndexesTemplate method if this table requires indexed columns
		
		if(isset($object->indexes[$tableName])) 
		{
			$this->createIndexesTemplate($object, $tableName, $object->indexes[$tableName]);
		}
	}


	/**
	 * Creates the database view required to optimize access to localized object
	 *
	 * @param 	object 	$object 			The object		 
	 */
	
	private function createView(&$object)
	{
		// Gather static + localized properties
		
		if ($object->isLocalized === true)
		{
			$properties = array_merge($object->staticProperties, $object->localizedProperties);
		}
		else
		{
			$properties = $object->staticProperties;
		}


		// Make sure all fields are protected by ``

		$fields = array();

		foreach ($properties as $propertyName => $property)
		{
			// Add the property to fields

			$fields[] = '`' . $propertyName . '`';


			// If it's a timestamp, add a field with the date equivalent

			if ($property['type'] === TYPE_TIMESTAMP)
			{
				$fieldName = str_replace
				(
					array('time', 'Time'),
					array('date', 'Date'),
					$propertyName
				);

				$fields[] = 'FROM_UNIXTIME(`' . $propertyName . '`) AS `' . $fieldName . '`';
			}
		}
		
		
		// Prepare the SQL query
		
		$query =
		"
		CREATE OR REPLACE VIEW `%1`.`%2` AS
		SELECT " . implode(', ', $fields) . "
		FROM `%1`.`%3`
		";

		if ($object->isLocalized === true)
		{
			$query .=
			"
			LEFT JOIN `%1`.`%4`
			ON `%1`.`%3`.id = `%1`.`%4`.`%5`
			";
		}
		
		$queryVariables = array
		(
			$object->databaseName,
			$object->viewName,
			$object->tableName,
			$object->tableName_L,
			$object->idName
		);


		// Create the view

		$object->databaseDriver->executeQuery($query, $queryVariables);
	}


	/**
	 * Duplicate an object
	 *
	 * @param object  $object 		The object to duplicate, pass py reference
	 * @param boolean $isRecursive  Do we want to duplicate only the object, or his relations too
	 *
	 * @return int The id of the duplicate object
	 */
	
	public function saveAs(&$object, $isRecursive = false)
	{	

		// Reset the ID + reference
			
		$object->staticProperties['id']['value'] = null;
		$object->staticProperties['reference']['value'] = null;
		$object->staticProperties['externalId']['value'] = null;
		$object->staticProperties['externalSource']['value'] = null;


		// Is it recursive ?

		if ($isRecursive == true)
		{
			foreach ($object->staticProperties as $propertyName => $propertyDetails)
			{
				$action = 'set' . ucfirst($propertyName);

				if 
				(
					is_null($propertyDetails['value']) === true  || 
					empty($propertyDetails['value']) === true
				)
				{
					continue;
				}


				// Is there an aggregation or an association ?

				if 
				(
					$propertyDetails['isAssociation'] == true || 
					$propertyDetails['isAggregation'] == true ||
					$propertyDetails['isAncester'] == true
				)
				{
					// Case core properties, pass

					if ($propertyDetails['isCore'] == true)
					{
						$object->$action(null);			

						continue;
					}				


					// Get the object associate

					// Construct the path to the file

					require_once($propertyDetails['pathToExtension'] . 'Model/' . $propertyDetails['className'] . '.php')	;


					// Get the classname

					$className = $propertyDetails['className'];


					// Does the class exist ?

					if (class_exists($className) === false)
					{
						// No, construct it with a namespace

						$className = str_replace('-', '\\', $propertyDetails['extensionId']) . '\\' . $className;
					}


					// Create the object

					$objectAssociation = new $className();


					// Try

					try 
					{
						// Load the association object

						$objectAssociation->load($propertyDetails['value']);


						// Save it recursively

						$idAssociation = $objectAssociation->saveAs(true);

						
						// Set the new id 

						$object->$action($idAssociation);						
					}
					catch (\Exception $e)
					{
						$object->$action(null);			
					}
				}	
			}	
		}

		// Save the new object

		$object->save();
			

		return $object->getId();		

	}


	/**
	 * Export objects
	 *
	 * @param 	string 		$timezone 			The timezone
	 * @param 	array 		$extensionIds 		The extensions ids to export
	 * @param 	boolean 	$isExclusive 		Is exclusive or not
	 * @param 	boolean 	$isIncremental 		Is incremental or not	 
	 *		 
	 */

	public function export($timezone, $extensionIds = null, $isExclusive = true, $isIncremental = true)
	{

		// Is there extension ids ?

		if (is_null($extensionIds) === true)
		{
			$resultExtensionIds = app()->getExtensionManager()->getExtensionsIds();

			// Get extensions that are shared

			$nbExtensions = count($resultExtensionIds);
			$sharedExtensionsIds = array();

			for ($i = 0; $i < $nbExtensions; $i++)
			{
				$extensionId = $resultExtensionIds[$i];

				if (pref('[' . $extensionId . ']/database/isShared') == true)
				{
					$sharedExtensionsIds[] = $extensionId;
					unset($resultExtensionIds[$i]);
				}	
			}	

			foreach ($resultExtensionIds as $extensionId) 
			{
				$extensionIds[$extensionId] = null;
			}
		}


		//

		$exceptions = array();


		// For each extension 

		foreach ($extensionIds as $extensionId => $classNames)
		{
			// Is there models ?

			if (is_array($classNames) === false)
			{;
				$namespace = str_replace('-', '\\', $extensionId);

				$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($extensionId) . 'Model/';
				$classNames = app()->getModelManager()->getModels($namespace, $pathToExtension);
			}
			

			// For each model

			foreach ($classNames as $key => $className)
			{
				// Build objects

				$object = $this->buildObject($extensionId, $className);


				// Call export function

				$exceptions[] = $this->processExportByClassName($timezone, $object);
				
			}
		}


		return $exceptions;
	}


	/**
	 * Export an object
	 *
	 * @param 	string 		$timezone 			The timezone
	 * @param 	object 		$object 			The object	 
	 *		 
	 */

	public function exportObject($timezone, &$object)	
	{
		// Create variables

		$exceptions = array();


		// Call export function

		$exceptions[] = $this->processExportByClassName($timezone, $object);


		return $exceptions;
	}


	/**
	 * Fixes a list of localization missed for an object
	 *
	 * @param 	object 		$object 			The object		 
	 * @param	int $id 						Id of object to check
	 * @param	array $missedLocalization		List of all languageIsoCode missing for an object
	 * @param	bool	$isValidated	True to validate the raw object, false to ignore issues
	 *
	 * @return	int 							Number of languages fixed
	 */

	public function fixLocalizations(&$object, $objectId, $languages, $isValidated = true)
	{
		// If it's not a localized object, there's nothing we can do...
		
		if ($object->isLocalized == false)
		{
			return;
		}


		// Parse each languages and try to fix it

		$nbLanguages = 0;
		
		
		foreach ($languages as $languageIsoCode)
		{
			// We ensure language doesn't exist for an object

			$rawObjects = $object->find
			(
				array
				(
					'conditions' => array
					(
						'id' => array(EQUAL, $objectId),
						'languageIsoCode' => array(EQUAL, $languageIsoCode)
					)
				)
			);


			// If language doesn't exist, we save it.

			if (empty($rawObjects) == true)
			{
				$object->insertTemplate(true, $languageIsoCode);
				$nbLanguages++;
			}
		}


		// Check that object has been fixed
		if 
		(
			($nbLanguages == 0) && 
			(count($languages) > 0) &&
			($isValidated == true)
		)
		{
			e
			(
				EXCEPTION_OBJECT_FIX_LOCALIZATIONS_FAILED,
				array
				(
					'className' => $object->className,
					'objectId' => $objectId,
					'nbLanguages' => $nbLanguages,
					'languages' => print_r($languages, true)
				)
			);
		}

		return $nbLanguages;
	}


	/**
	 * Installs an object, i.e. creates tables and views
	 *
	 * @param 	object 		$object 			The object		 
	 */
	
	public function forge(&$object)
	{
		// Create database

		$this->createDatabase($object->extensionId);


		// Grant permissions

		if (env() === 'Local')
		{
			$this->grantPermissions($object->extensionId);
		}
		

		// Create the table and view

		$this->createTable($object);
		$this->createView($object);

		
		// Maybe features have something to forge

		foreach ($object->features as $extensionId => $features)
		{
			foreach ($features as $featureName)
			{
				try
				{
					$feature = feat($extensionId, $featureName);

					if (is_callable(array($feature, 'forge')) == true)
					{
						$feature->forge($object);
					}	
				}
				catch(\Exception $e)
				{
				}
			}
		}
	}


	/**
	 * Get the fields
	 *
	 * @param 	object 		$object 			The object		 
	 */

	public function getFields(&$object)
	{

		// Define properties

		$objectProperties = array_merge($object->staticProperties, $object->localizedProperties);


		//

		$fieldsCustom = array();


		// Convert properties into fields

		foreach ($objectProperties as $propertyName => $property)
		{
			// Ignore aggregations / compositions / core properties

			if
			(
				($property['isAggregation'] === true) ||
				($property['isComposition'] === true) ||
				($property['isCore'] === true)
			)
			{
				continue;
			}


			// Define the field name and value

			$fieldName = $propertyName;


			// Convert timestamps to dates

			if ($property['type'] == TYPE_TIMESTAMP)
			{
				$fieldName = str_replace('timeOf', 'dateOf', $propertyName);
			}


			// Record the field

			if ($property['isCustom'] === true)
			{
				$fieldsCustom[$fieldName] = "";
			}
			else
			{
				$fields[$fieldName] =  "";
			}
		}


		// Sort fields

		if (defined('SORT_NATURAL') === true)
		{
			ksort($fields, SORT_NATURAL);
		}
		else
		{
			ksort($fields);
		}

		// Define core fields

		$coreFields = array
		(
			'action' => '',
			'source' => '', 
			'id' => '',
			'dateOfCreation' => '',
		);


		// Add core fields at the beginning of fields

		$fields = array_merge($coreFields, $fields, $fieldsCustom);	


		return $fields;
	}


	/**
	 * Construct the reflection of an objet and the path
	 *
	 * @param string 	$extensionId 		The extension id
	 * @param string 	$className 	 		The class name without namespace
	 * @param string 	$directory 			The directory
	 * @param object 	$objectReflection 	The object reflection , pass by reference
	 */

	public function getObjectReflection($extensionId, $className, $directory, &$objectReflection, &$pathToFile)
	{
		// Get the path to extension

		$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($extensionId);


		// Construct the name of the file to documentate

		$fileName = $className . '.php';						


		// Define the path to the file to documentate

		$pathToFile = $pathToExtension . $directory . '/' . $fileName;


		// Require the file

		require_once($pathToFile);		


		// Construct the object

		$objectName = $className;


		// Is the class existing?

		if (class_exists($objectName) === false)
		{
			// No, add namespace

			$namespace = str_replace('-', '\\', $extensionId);

			$objectName = $namespace . '\\' . $className;
		}


		// Instanciate the object reflection class

		$objectReflection = new \ReflectionClass($objectName);		
	}

	/**
	* Grant the permissions to a user on a database
	*
	* @param 	string 	$extensionId 	The extension id
	* @param 	string 	$databaseName 	The name of the database
	*/

	public function grantPermissions($extensionId, $databaseName = null)
	{
		if (pref('[' . $extensionId . ']/database/isShared') == true)
		{
			return;
		}


		// Connect with loop parameters

		$databaseDriver = db($extensionId);

		$databaseDriver->connect(pref('[Loop]/database/host'), pref('[Loop]/database/root/login'),  pref('[Loop]/database/root/password'));


		// Define the database name
			
		if (is_null($databaseName) === true)
		{
			$databaseName = pref('[' . $extensionId . ']/database/name');
		}


		$user = pref('[' . $extensionId . ']/database/login');


		// Create the user
				
		try
		{
			$query = 'CREATE USER `%1`';

			$queryVariables = array
			(
				$user,
			);

			$databaseDriver->executeQuery($query, $queryVariables);
		}
		catch (\Exception $e)
		{
			// If the user already exist, there is an exception

			
		}


		// Grant the privileges
		
		$query = 'GRANT ALL PRIVILEGES ON `%1`.* TO `%2`';

		$queryVariables = array
		(
			$databaseName,
			$user,
		);

		$databaseDriver->executeQuery($query, $queryVariables);
	}

	
	/**
	 * Import objects
	 *
	 * @param 	string 		$timezone 			The timezone
	 * @param 	array 		$extensionIds 		The extensions ids to export
	 * @param 	boolean 	$isExclusive 		Is exclusive or not
	 * @param 	boolean 	$isIncremental 		Is incremental or not	 
	 * @param 	boolean 	$isStrict 			Is strict or not for the action
	 *		 
	 */

	public function import($timezone, $extensionIds = null, $isExclusive = true, $isIncremental = true, $isStrict = true)
	{
		// Create stack_tmp table 

		$query =
		"
		CREATE TABLE IF NOT EXISTS `stack_tmp` (
		  `id` int(10) NOT NULL AUTO_INCREMENT,
		  `extensionId` varchar(255) NOT NULL,
		  `model` varchar(255) NOT NULL,
		  `value` text NOT NULL,
		   `timezone` varchar(255) NOT NULL,
		  `is_complete` tinyint(1) NOT NULL,
		  PRIMARY KEY (`id`)
		) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
		";


		// Get the databaseDriver

		$databaseDriver = app()->getDatabaseDriver(EXTENSION_APPLICATION);

		// Execute query

		$databaseDriver->executeQuery($query, array());

		
		// Is there extension ids ?

		if (is_null($extensionIds) === true)
		{
			$resultExtensionIds = app()->getExtensionManager()->getExtensionsIds();	


			// Get extensions that are shared

			$nbExtensions = count($resultExtensionIds);
			$sharedExtensionsIds = array();


			// For each extension found

			for ($i = 0; $i < $nbExtensions; $i++)
			{
				$extensionId = $resultExtensionIds[$i];

				if (pref('[' . $extensionId . ']/database/isShared') == true)
				{
					$sharedExtensionsIds[] = $extensionId;
					unset($resultExtensionIds[$i]);
				}	
			}	

			foreach ($resultExtensionIds as $extensionId) 
			{
				$extensionIds[$extensionId] = null;
			}
		}


		// For each extension 

		foreach ($extensionIds as $extensionId => $classNames)
		{
			// Is there models ?

			if (is_array($classNames) === false)
			{
				$namespace = str_replace('-', '\\', $extensionId);

				$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($extensionId) . 'Model/';
				$classNames = app()->getModelManager()->getModels($namespace, $pathToExtension);
			}
			

			// For each model

			foreach ($classNames as $key => $className)
			{
				$object = $this->buildObject($extensionId, $className);
				$exceptionsResult[] = $this->processImportByClassName($timezone, $object, $databaseDriver, $isStrict);
			}
		}


		// Is there a stack ?

		$exceptionsResult[] = $this->processStack();


		// Delete table stack

		$query ="DROP TABLE `stack_tmp`";

		$databaseDriver->executeQuery($query, array());



		return $exceptionsResult;
	}


	/**
	 * Import  an object
	 *
	 * @param 	string 		$timezone 			The timezone
	 * @param 	object 		$object 			The object	 
	 */

	public function importObject($timezone, &$object)
	{
		// Create stack_tmp table 

		$query =
		"
		CREATE TABLE IF NOT EXISTS `stack_tmp` (
		  `id` int(10) NOT NULL AUTO_INCREMENT,
		  `extensionId` varchar(255) NOT NULL,
		  `model` varchar(255) NOT NULL,
		  `value` text NOT NULL,
		   `timezone` varchar(255) NOT NULL,
		  `is_complete` tinyint(1) NOT NULL,
		  PRIMARY KEY (`id`)
		) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
		";


		// Get the databaseDriver

		$databaseDriver = app()->getDatabaseDriver(EXTENSION_APPLICATION);

		// Execute query

		$databaseDriver->executeQuery($query, array());

		$this->processImportByClassName($timezone, $object, $databaseDriver);
		

		// Is there a stack ?

		$exceptionsResult[] = $this->processStack();


		// Delete table stack

		$query ="DROP TABLE `stack_tmp`";

		$databaseDriver->executeQuery($query, array());



		return $exceptionsResult;
	}


/**
	 * Template function used to create a new instance of the object
	 *
	 * @param	bool	$isLocalized		True to insert into the _L table, false for the main one
	 * @param	string	$languageIsoCode	The code of the language in which the object needs to be inserted (can be null, ignored if not $isLocalized))
	 */
	
	protected function insertTemplate($isLocalized = false, $languageIsoCode = null)
	{
		// Do we have the right to perform this action on the class?

		app()->getSecurityManager()->grantPermissionOnClass($this->className, ACTION_INSERT);
		
		
		// Should we work on the localized properties?
		
		if ($isLocalized == true)
		{
			// For _L tables we need to set the language code and the id<Object>
			
			$this->setPropertyValue('languageIsoCode', $languageIsoCode, $languageIsoCode, true);
			$this->setPropertyValue($this->idName, $this->getId(), $languageIsoCode, true);

			
			// Use localized table + properties
			
			$properties = &$this->localizedProperties;
			$tableName = $this->tableName_L;
		}
		else
		{
			// Use non-localized table + properties
			
			$properties = &$this->staticProperties;
			$tableName = $this->tableName;
			
			
			// For core tables we need to set the time of creation and the user who created the object
			
			$this->setReference(app()->getStringHelper()->generateUniqueKey(8));
			$this->setSourceOfCreation($this->extensionId);
			$this->setTimeOfCreation(time());
			$this->setUserCreatorId(user()->getId());
		}
		
		
		// Parse each property
		
		$names = array();

		$value = null;
		$values = array();

		$queryVariables = array($this->databaseName . '`.`' . $tableName);
		$i = count($queryVariables) + 1;

		
		foreach ($properties as $propertyName => $propertyOptions)
		{
			// The value of the property
			
			if ($propertyOptions['isLocalized'] == true)
			{
				if (isset($propertyOptions['value'][$languageIsoCode]) == false)
				{
					continue;	
				}
				
				$value = $propertyOptions['value'][$languageIsoCode];
			}
			else
			{
				$value = $propertyOptions['value'];
			}
			

			
			// Remember property value
			
			$names[] = '`' . $propertyName . '`';
			$values[] = '%' . $i++;
			
			if ($value === null)
			{
				$queryVariables[] = null;
			}
			else
			{
				$queryVariables[] = $value;
			}
		}

		
		// Build SQL query
		
		$query = 'INSERT INTO `%1`(' . implode(', ', $names) . ') VALUES (';
		$separator = null;

		for ($j = 1; $j < ($i - 1); $j++)
		{
			$query .= $separator;

			if ($queryVariables[$j] == null)
			{
				$query .= 'NULL';
			}
			else
			{
				$query .= '\'' . $values[$j - 1] . '\'';
			}

			$separator = ', ';
		}

		$query .= ')';


		// Execute it
		
		try
		{
			$this->databaseDriver->executeQuery($query, $queryVariables);
		}
		catch (\Exception $e)
		{
			// get the context

			$context = $e->getContext();

			// If there's a problem with orphans localized fields, remove it and test again the query

			if
			(
				($isLocalized == true) &&
				($e->getCode() == EXCEPTION_DATABASE_QUERY_FAILED) &&
				(isset($context['code']) == true) &&
				($context['code'] == 23000) // Error code sql 23000 is a duplicate entry problem in mysql
			)
			{
				// Prepare query to delete orphans loacalized fields

				$queryDeleteOrphanLocalized = 'DELETE FROM `%1` WHERE `%2` = \'%3\'';
				$queryOrphanLocalizedVariables = array($this->databaseName . '`.`' . $tableName, $this->idName, $this->getId());

				$this->databaseDriver->executeQuery($queryDeleteOrphanLocalized, $queryOrphanLocalizedVariables);

				// Test insert query.

				$this->databaseDriver->executeQuery($query, $queryVariables);
			}
			else
			{
				throw $e;
			}			
		}

		// If the object didn't have an ID, use the one created by the database
		
		if
		(
			($isLocalized == false) &&
			($this->isLoaded() == false)
		)
		{
			$this->setId($this->databaseDriver->getLastId());
		}
		
		
	}

	
	/**
	 * Loads an object from a raw object (associative array)
	 *
	 * @param 	object 		$object 		The object		 
	 * @param	array		$rawObject		The raw form of the object
	 * @param	bool		$isValidated	True to validate the raw object, false to ignore issues
	 */
	
	public function loadFromRawObject(&$object, &$rawObject, $isValidated = true)
	{
		// Reset the object (needs to be fresh new)

		$object->reset();
		

		// We need to parse every property of the object
		
		foreach ($object->staticProperties as $propertyName => &$property)
		{
			if (isset($rawObject[$propertyName]) == true)
			{

				try
				{
					// Set the property value

					$object->setPropertyValue($propertyName, $rawObject[$propertyName], null, false);
				}
				catch (\Exception $e)
				{
					// Should we really trigger an exception?
					
					if
					(
						($isValidated == true) &&
						($e->getCode() != EXCEPTION_OBJECT_PROPERTY_VALUE_REQUIRED) &&
						($e->getCode() != EXCEPTION_OBJECT_PROPERTY_VALUE_NOT_VALID)
					)
					{
						throw $e;
					}
				}
			}
			else
			{
				// Otherwise, it's like a reset
				
				$property['value'] = null;
				$property['hasChanged'] = null;
			}
		}
		
		$object->parseLocalizedProperties($rawObject);
		
		// And finally load smart properties
		
		foreach ($object->smartProperties as $smartPropertyName => &$smartProperty)
		{
			if (isset($rawObject[$smartPropertyName]) == true)
			{
				try
				{
					// Load the value from the raw object
					
					$object->setPropertyValue($smartPropertyName, $rawObject[$smartPropertyName], null, false);
				}
				catch (\Exception $e)
				{
					// Should we really trigger an exception?
					
					if
					(
						($isValidated == true) &&
						($e->getCode() != EXCEPTION_OBJECT_PROPERTY_VALUE_REQUIRED) &&
						($e->getCode() != EXCEPTION_OBJECT_PROPERTY_VALUE_NOT_VALID)
					)
					{
						throw $e;
					}
				}
			}
			else
			{
				// Otherwise, it's like a reset
				
				$smartProperty['value'] = null;
				$smartProperty['hasChanged'] = null;
			}
		}
	}


	/**
	 * Process core fields
	 *
	 * @param 	object 		$object 		The object	
	 * @param 	array 		$fields 		The fields	
	 * @param 	string 		$timezone 		The timezone	
	 *
	 */

	private function processCoreFields(&$object, &$fields, $timezone)
	{
		// Define core fields

		$coreFields = array
		(
			'action' =>
			"
			COALESCE
			(
				IF
				(
					`" . $object->databaseName . "`.`" . $object->viewName . "`.`timeOfDeletion` IS NOT NULL,
					'D',
					IF
					(
						`" . $object->databaseName . "`.`" . $object->viewName . "`.`timeOfUpdate` IS NOT NULL,
						'U',
						'C'
					)
				),
				'C'
			)
			AS `action`
			",
			'source' =>
			"
			COALESCE
			(
				`" . $object->databaseName . "`.`" . $object->viewName . "`.`externalSource`,
				COALESCE
				(
					'" . $object->extensionId . "',
					''
				)
			)
			AS `source`
			",
			'id' =>
			"
			COALESCE
			(
				`" . $object->databaseName . "`.`" . $object->viewName . "`.`externalId`,
				COALESCE
				(
					`" . $object->databaseName . "`.`" . $object->viewName . "`.`id`,
					''
				)
			)
			AS `id`
			",
			'dateOfCreation' =>
			"
			COALESCE
			(
				CONVERT_TZ
				(
					FROM_UNIXTIME(`" . $object->databaseName . "`.`" . $object->viewName . "`.`externalTimeOfCreation`),
					'GMT',
					'" . $timezone . "'
				),
				''
			)
			AS `dateOfCreation`",			
		);


		// Add core fields at the beginning of fields

		$fields = array_merge($coreFields, $fields);
	}


	/**
	* Export by class name
	*
	* @param 	string 		$timezone 		The timezone
	* @param 	object 		$object 		The object		
	*/

	private function processExportByClassName($timezone, &$object)
	{
		$exceptions = array();

		try
		{
			$etl = $this->buildEtl($object);
			$pathToCsv = $this->buildPathToCsv($object, 'Export');
			

			// Initialize variables

			$fields = null;
			$from = null;
			$where = null;


			// Process the export query
					
			$this->processFields($object, $timezone, $fields, $from, $where, ACTION_EXPORT);
			$this->processCoreFields($object, $fields, $timezone);
			$this->processFrom($object, $from);
			$this->processWhere($object, $where);

					
			// Do we have an ETL?

			if (is_object($etl) === true)
			{
				$etl->process($object, $timezone, $fields, $from, $where);
			}


			// Prepare the query
			// @todo Manage conditions to make it incremental (OUTER JOIN with last export)
			// @todo Manage conditions to make it exclusive

			$query =
			"
			SELECT
				'" . implode("',\n'", array_keys($fields)) . "'
			UNION
			SELECT
				" . implode(",\n", array_values($fields)) . "
			INTO OUTFILE
				'%1'
			FIELDS
				TERMINATED BY ';'
				OPTIONALLY ENCLOSED BY '\"'
				LINES TERMINATED BY '\n'
			FROM
				" . implode(",\n", $from) . "
			WHERE
				" . implode(" AND\n", $where) . "
			";

			$queryVariables = array
			(
				$pathToCsv
			);


			// Delete the previous CSV file

			if (file_exists($pathToCsv) === true)
			{
				unlink($pathToCsv);
			}


			// Let's do it!

			$object->databaseDriver->executeQuery($query, $queryVariables);
		}
		catch(\Exception $e)
		{
			$exceptions[] = $e;
		}


		return $exceptions;
	}


	/**
	*  Process the fields
	*
	* @param 	object 		$object 		The object		
	* @param 	string 		$timezone 		The timezone	
	* @param 	array 		$fields 		The fields	
	* @param 	array 		$from 			The from		
	* @param 	array 		$where 			The where		
	* @param 	string 		$direction 		The direction import/export
	*/


	private function processFields(&$object, $timezone, &$fields, &$from, &$where, $direction)
	{					
		// Make sure $fields is an array

		if
		(
			(is_array($fields) === false) ||
			(empty($fields) === true)
		)
		{
			$fields = array();
		}


		// Define properties

		$objectProperties = array_merge($object->staticProperties, $object->localizedProperties);


		//

		$fieldsCustom = array();


		// Convert properties into fields

		foreach ($objectProperties as $propertyName => $property)
		{
			// Ignore aggregations / compositions / core properties

			if
			(
				($property['isAggregation'] === true) ||
				($property['isComposition'] === true) ||
				($property['isCore'] === true)
			)
			{
				continue;
			}


			// Define the field name and value

			$fieldName = $propertyName;
			$fieldValue =
			"
			COALESCE
			(
				`" . $object->databaseName . "`.`" . $object->viewName . "`.`" . $propertyName . "`,
				''
			)
			";


			// Convert timestamps to dates

			if ($property['type'] == TYPE_TIMESTAMP)
			{
				$fieldName = str_replace('timeOf', 'dateOf', $propertyName);

				$fieldValue =
				"
				COALESCE
				(
					CONVERT_TZ
					(
						FROM_UNIXTIME(`" . $object->databaseName . "`.`" . $object->viewName . "`.`" . $propertyName . "`),
						'" . pref('[Loop]/localization/timezone') . "',
						'" . $timezone . "'
					),
					''
				)
				";
			}


			// For associations, use external ID as ID

			if ($property['isAssociation'] === true)
			{

				// ID / external ID

				$association = $this->buildObject($property['extensionId'], $property['className']);

				$fieldValue =
				"
				COALESCE
				(
					(
						SELECT
							`externalId`
						FROM
							`" . $association->databaseName . "`.`" . $association->viewName . "`
						WHERE
							`" . $association->databaseName . "`.`" . $association->viewName . "`.`id` = `" . $object->databaseName . "`.`" . $object->viewName . "`.`" . $propertyName . "`
					),
					COALESCE
					(
						`" . $object->databaseName . "`.`" . $object->viewName . "`.`" . $propertyName . "`,
						''
					)
				)
				";


				// If property is ancester, export it too

				if ($property['isAncester'] === true)
				{
					// Process the export query
	
					if ($direction == ACTION_EXPORT)
					{
						$this->export($timezone, array($property['extensionId'] => array($property['className'])));
					}
				}
			}


			// Record the field

			if ($property['isCustom'] === true)
			{
				$fieldsCustom[$fieldName] = $fieldValue . ' AS `' . $fieldName . '`';
			}
			else
			{
				$fields[$fieldName] =  $fieldValue . ' AS `' . $fieldName . '`';
			}		
		}


		// Sort fields

		if (defined('SORT_NATURAL') === true)
		{
			ksort($fields, SORT_NATURAL);
		}
		else
		{
			ksort($fields);
		}


		// Add core fields at the beginning of fields

		$fields = array_merge($fields, $fieldsCustom);			
	}


	/**
	 * Processes a single find condition
	 *
	 * @param 	object 		$object 		The object	 
	 * @param	string	$conditionName	The name of the condition (usually the property name of an object)
	 * @param	array		$condition		An array containing the condition (operator and value)
	 * @param	array		$conditions		An array of SQL fragments (conditions translated into SQL)
	 */
	
	public function processFindCondition(&$object, $conditionName, $condition, &$queryFragments)
	{		
		// Get the operator and the value
		
		$conditionValue = array_pop($condition);
		$conditionOperator = array_pop($condition);
		
		
		// Ensure the value is secure and valid

		if (is_array($conditionValue) == true)
		{
			// Secure the value of each item in the array
			
			foreach ($conditionValue as &$value)
			{
				// Every value will be between single quotes
				
				$value = '\'' . $object->databaseDriver->secureString($value) . '\'';
			}
		}
		else
		{
			// If not an array, the value MUST be a string

			$conditionValue = strval($conditionValue);
		}


		// Make sure we've got the right property value
		
		if
		(
			($conditionOperator == IN) ||
			($conditionOperator == NOT_IN)
		)
		{
			// For IN operator, if the list of values is empty simply skip it
			
			if
			(
				(is_array($conditionValue) == false) ||
				(empty($conditionValue) == true)
			)
			{
				return;
			}
			
			
			// Will look like IN ('%1', '%2', '%3')
			
			$conditionValue = '(' . implode(', ', $conditionValue) . ')';
		}
		else if ($conditionOperator == BETWEEN)
		{
			// Will look like BETWEEN '%1' AND '%2'
			
			$conditionValue = implode(' AND ', $conditionValue);
		}
		else if
		(
			($conditionValue == IS_NULL) ||
			($conditionValue == IS_NOT_NULL)
		)
		{
			$conditionValue = $object->databaseDriver->secureString($conditionValue);

			$conditionOperator = null;
		}
		else if (is_array($conditionValue) == false)
		{
			$conditionValue = '\'' . $object->databaseDriver->secureString($conditionValue) . '\'';
		}
		else
		{			
			e
			(
				EXCEPTION_OBJECT_CONDITION_NOT_VALID,
				array
				(
					'className' => $object->className,
					'propertyName' => $conditionName,
					'condition' => print_r($condition, true)
				)
			);
		}
		

		// Write the condition in SQL
		
		$queryFragments[] = '(`%1`.`%2`.`' . $object->databaseDriver->secureString($conditionName) . '` ' . $conditionOperator . ' ' . $conditionValue . ')';
	}
	

	/**
	 * Processes all conditions contained in options of a find
	 * @see CObject::find
	 */
	
	public function processFindConditions(&$object, $options, &$queryFragments)
	{
		// Should we select deleted objects too?
		
		if ($options['acceptDeletedObjects'] == false)
		{
			$options['internalConditions']['timeOfDeletion'] = array(EQUAL, IS_NULL);
		}

		$this->checkSpecialOptions($object, $options);
		

		// What's the global relational operator?
		
		if (empty($options['logicalOperator']) == true)
		{
			$options['logicalOperator'] = LOOP_AND;
		}
		
		
		// Now process all conditions
		
		$conditions = array();
		$nbConditions = 0;
		$subQueryFragments = array();
		
		for ($i = 0; $i < 2; $i++)
		{
			// Define target options
			
			$subQueryFragments[$i] = array();
			
			if ($i == 0)
			{
				$targetOptions = &$options['internalConditions'];
			}
			else
			{
				$targetOptions = &$options['conditions'];
			}
			
			
			// Parse each condition, if any
			
			if (is_array($targetOptions) == false)
			{
				continue;
			}
			
			foreach ($targetOptions as $propertyName => $condition)
			{
				// Make sure the condition is valid
				
				if
				(
					(is_array($condition) == false) ||
					(count($condition) != 2)
				)
				{
					e
					(
						EXCEPTION_OBJECT_CONDITION_NOT_VALID,
						array
						(
							'className' => $object->className,
							'condition' => print_r($condition, true)
						)
					);
				}
				
				
				// Ignore smart properties
				
				$property = $object->getProperty($propertyName);

				if ($property['isSmart'] == true)
				{
					continue;
				}
				
				
				// Good, we have a condition!
				
				$nbConditions++;
				
				
				// Is it a simple condition or a group of conditions?
							
				$conditionOperator = $condition[0];

				if
				(
					($conditionOperator == LOOP_OR) ||
					($conditionOperator == LOOP_AND)
				)
				{
					
					// If it's a group, process each condition one by one

					$subSubSqlFragments = array();
					
					foreach ($condition[1] as $subCondition)
					{
						$this->processFindCondition($object, $propertyName, $subCondition, $subSubSqlFragments);
					}
					
					
					// And then add the result to the SQL fragments
					
					$subQueryFragments[$i][] = '(' . implode(' ' . $conditionOperator . ' ', $subSubSqlFragments) . ')';
				}
				else
				{
				
					// If it's a single condition simply process it
					
					$this->processFindCondition($object, $propertyName, $condition, $subQueryFragments[$i]);
				}
			}
			
		}

			
		// In case we have conditions, add a WHERE statement
		
		if ($nbConditions > 0)
		{
			$queryFragments[] = 'WHERE';
			$separator = null;
			
			if (empty($subQueryFragments[0]) == false)
			{
				$queryFragments[] = '(' . implode(' ' . LOOP_AND . ' ', $subQueryFragments[0]) . ')';
				$separator = ' AND ';
			}
			
			if (empty($subQueryFragments[1]) == false)
			{
				$queryFragments[] = $separator . '(' . implode(' ' . $options['logicalOperator'] . ' ', $subQueryFragments[1]) . ')';
			}
		}		
	}


	/**
	 * Processes GROUP BY conditions in find options
	 * @see CObject::processFindOptions
	 */
	
	public function processFindGroupBy($options, &$queryFragments)
	{
		if (isset($options['groupBy']) == true)
		{
			$queryFragments[] = 'GROUP BY `%1`.`%2`.`' . implode('`, `%1`.`%2`.`', $options['groupBy']) . '`';
		}
	}


	/**
	 * Processes the LIMIT condition in find options
	 * @see CObject::processFindOptions
	 */
	
	public function processFindLimit(&$object, $options, &$queryFragments)
	{
		// Do we have a limit defined?
		
		$nbLimits = count($options['limit']);
		
		if ($nbLimits < 2)
		{
			return;
		}
		
		
		// Do we have a localized property?
		
		if ($object->isLocalized == true)
		{
			// We need to multiply limits by the number of languages
			
			$languages = pref('[Loop]/localization/languages');
			$nbLanguages = count($languages);

			for ($i = 0; $i < $nbLimits; $i++)
			{
				$options['limit'][$i] = $options['limit'][$i] * $nbLanguages;
			}
		}
		
		
		// Add the SQL fragment with the LIMIT clause
		
		$queryFragments[] = 'LIMIT ' . $object->databaseDriver->secureString(implode(',', $options['limit']));
	}
	

	/**
	 * Processes options of a find()
	 *
	 * @param	array	$options			The options for the find, can contain the following values
	 *
	 * - acceptDeletedObjects: true to include deleted objects, false otherwise
	 * - acceptDisabledObjects: true to include disabled objects, false otherwise
	 * - conditions: things to put in the WHERE statement
	 * - limits: min/max limits
	 * - properties: the list of properties to load in the raw object
	 * - orderBy: how should the results be sorted out?
	 * - orderDirection: should ordering be descendent or ascendent?
	 * - isOrderedRandomly: true if ordered randomly, false otherwise
	 *
	 * @param 	object 		$object 		The object	 
	 * @param	array	$properties			The list of final properties to be selected
	 * @param	array	$smartProperties	The list of smart properties to be computed
	 * @param	array	$queryFragments		A list of SQL statements (e.g. LIMIT, ORDER BY, etc..)
	 */
	
	public function processFindOptions(&$object, &$options, &$properties, &$smartProperties, &$queryFragments)
	{
		// Create an array that contains all possible options with default value
		
		$defaultOptions = array
		(
			'acceptDeletedObjects' => null,
			'acceptDisabledObjects' => null,
			'acceptTemporaryObjects' => null,
			'conditions' => null,
			'excludeId' => null,
			'internalConditions' => null,
			'isOrderedRandomly' => null,
			'limit' => null,
			'logicalOperator' => null,
			'orderBy' => null,
			'orderDirection' => null,
			'properties' => null,
			'smartProperties' => null
		);
		
		
		// Merge options received with default options
		
		if (is_array($options) == true)
		{
			$options = array_merge($defaultOptions, $options);
		}
		else
		{
			$options = $defaultOptions;
		}
		

		// Process properties (SELECT)
		
		$this->processFindProperties($object, $options, $properties, $smartProperties);
		
		
		// Process conditions (WHERE)
		
		$this->processFindConditions($object, $options, $queryFragments);
		
		
		// Process GROUP BY
		
		$this->processFindGroupBy($options, $queryFragments);
		
		
		// Process ORDER BY
		
		$this->processFindOrderBy($options, $queryFragments);
		
		
		// Process LIMIT

		$this->processFindLimit($object, $options, $queryFragments);
	}


	/**
	 * Processes ORDER BY conditions in find options
	 * @see CObject::processFindOptions
	 */
	
	public function processFindOrderBy($options, &$queryFragments)
	{
		if
		(
			(isset($options['isOrderedRandomly']) == true) &&
			($options['isOrderedRandomly'] == true)
		)
		{
			$queryFragments[] = 'ORDER BY RAND()';
		}
		else if (isset($options['orderBy']) == true)
		{
			$queryFragments[] = 'ORDER BY `%1`.`%2`.`' . implode('`, `%1`.`%2`.`', $options['orderBy']) . '`';

			
			if (isset($options['orderDirection']) == true)
			{
				if ($options['orderDirection'] == 'ascendent')
				{
					$queryFragments[] = 'ASC';
				}
				else
				{
					$queryFragments[] = 'DESC';
				}
			}
			else
			{
				$queryFragments[] = 'DESC';
			}
		}
	}


	/**
	 * Processes properties for a find
	 * @see CObject::processFindOptions
	 */
	
	public function processFindProperties(&$object, $options, &$properties, &$smartProperties)
	{
		// Create final properties

		$properties = array();
		$smartProperties = array();

		
		// If no property has been defined, add all properties known for the object
		
		if (empty($options['properties']) == true)
		{
			$options['properties'] = $object->propertiesNames;
		}


		// Handle languageIsoCode if object is localized

		if ($object->isLocalized == true)
		{
			$properties[] = 'languageIsoCode';
		}
		

		// Should we include the object ID?

		if ($options['excludeId'] == false)
		{
			$properties[] = 'id';
		}

		
		// Parse properties sent to the find function
		
		if (is_array($options['properties']) == true)
		{
			foreach ($options['properties'] as $propertyName)
			{
				// Skip property if's a core one
				
				if (in_array($propertyName, $properties) == true)
				{
					continue;
				}
				
				
				// Skip property if name is reserved
				
				if ($propertyName == $object->idName)
				{
					continue;
				}
				
				
				// Store the property in the right container

				$properties[] = $propertyName;
			}
		}

				
		// Parse snart properties sent to the find function
		
		if (is_array($options['smartProperties']) == true)
		{
			$smartProperties = $options['smartProperties'];
		}
	}


	/**
	 * Formate the result of a sql query in raw object
	 *
	 * @param 	array 	$queryResults 	the result of a sql query
	 * @return 	array 	$rawObjects 	the raw object
	 */

	public function processFindResult(&$object, $queryResults)
	{
		$rawObjects = array();
		
		while ($rawObject = $object->databaseDriver->fetchAssociativeArray($queryResults))
		{
			// Get all values in different languages if object and field are localized

			if
			(
				($object->isLocalized == true) &&
				(isset($rawObject['nbObjects']) == false)
			)
			{
				foreach ($rawObject as $propertyName => $value)
				{
					// Find if a property is localized

					if ($propertyName == 'languageIsoCode')
					{
						continue;
					}
					
					// In place of value, insert all values in different languages supported

					$property = $object->getProperty($propertyName);
					
					if ($property['isLocalized'] == true)
					{
						if (array_key_exists($rawObject['id'], $rawObjects) == true)
						{
							$rawObjects[$rawObject['id']][$propertyName][$rawObject['languageIsoCode']] = $value;
						}
						else
						{
							$rawObject[$propertyName] = array($rawObject['languageIsoCode'] => $value);
						}
					}
				}
				
				// Remove languageIsoCode property to avoid exception in object version

				unset($rawObject['languageIsoCode']);


				// Make sure that raw object is always in array

				if (array_key_exists($rawObject['id'], $rawObjects) == false)
				{
					$rawObjects[$rawObject['id']] = $rawObject;
				}
			}
			else
			{
				if (isset($rawObject['id']) == true)
				{
					$rawObjects[$rawObject['id']] = $rawObject;
				}
				else
				{
					$rawObjects[] = $rawObject;
				}
			}
		}

		return $rawObjects;
	}


	/**
	 *
	 */

	private function processFrom($object, &$from)
	{
		// Make sure $from is an array

		if
		(
			(is_array($from) === false) ||
			(empty($from) === true)
		)
		{
			$from = array();
		}


		// Add the object table to the FROM

		$from[] = "`" . $object->databaseName . "`.`" . $object->viewName . "`";
	}


	/**
	 *
	 */

	private function processImportByClassName($timezone, &$object, $databaseDriver, $isStrict = true)
	{
		$exceptionsResult = array();

		$className = $object->classNameWithoutNamespace;
		$extensionId = $object->extensionId;

		// Build objects


		$etl = $this->buildEtl($object);
		$pathToCsv = $this->buildPathToCsv($object, 'Import');

		if (file_exists($pathToCsv) === false)
		{
			return;
		}


		// Is the file existing ?

		if (file_exists($pathToCsv) === false)
		{
			$exceptionsResult[] = array
			(
				EXCEPTION_FILE_NOT_FOUND,
				array 
				(
					'file' => $pathToCsv
				)
			);

			return;
		}


		// Initialize variables

		$fields = null;
		$from = null;
		$where = null;


		// Process the export query

		$this->processFields($object, $timezone, $fields, $from, $where, ACTION_IMPORT);
		$this->processCoreFields($object, $fields, $timezone);
		$this->processFrom($object, $from);
		$this->processWhere($object, $where);


		// Remove the temporary table

		$query =
		"
		DROP TABLE IF EXISTS
			`%1`.`%2_tmp`
		";

		$queryVariables = array
		(
			$object->databaseName,
			$object->viewName
		);

		$object->databaseDriver->executeQuery($query, $queryVariables);


		// Create a temporary table

		$query =
		"
		CREATE TABLE
			`%1`.`%2_tmp`
			(
				`" . implode('` VARCHAR(255) DEFAULT NULL, `', array_keys($fields)) . "` VARCHAR(255) DEFAULT NULL
					)
			ENGINE=InnoDB DEFAULT CHARSET=utf8
		";

		$queryVariables = array
		(
			$object->databaseName,
			$object->viewName
		);

		$object->databaseDriver->executeQuery($query, $queryVariables);


		// Get the environement

		$environement = env();
		$conditionEnv = '';

		if ($environement !== 'Local')
		{
			$conditionEnv = 'LOCAL';
		}


		// Insert data into our temporary table

		$query =
		"
		LOAD DATA 
		" . $conditionEnv . " INFILE
			'%1'
		INTO TABLE
			`%2`.`%3_tmp`
		FIELDS
			TERMINATED BY ';'
			OPTIONALLY ENCLOSED BY '\"'
			LINES TERMINATED BY '\n'
		IGNORE 1 LINES
		";

		$queryVariables = array
		(
			$pathToCsv,
			$object->databaseName,
			$object->viewName
		);

		$object->databaseDriver->executeQuery($query, $queryVariables);


		// Get every row

		$query =
		"
		SELECT
			*
		FROM
			`%1`.`%2_tmp`
		";

		$queryVariables = array
		(
			$object->databaseName,
			$object->viewName
		);

		$queryResults = $object->databaseDriver->executeQuery($query, $queryVariables);


		// Parse each row and insert / update / delete it

		while ($queryResult = $object->databaseDriver->fetchAssociativeArray($queryResults))
		{
			$condition = array();

			$object->reset();


			// Search if the externalId exist

			if (empty($queryResult['id']) === false)
			{
				$condition['externalId'] = array(EQUAL, $queryResult['id']);
			}


			// Search if the object exist

			if (empty($queryResult['source']) === false)
			{
				$condition['externalSource'] = array(EQUAL, $queryResult['source']);
			}


			// Find the results

			$rawResults = $object->find
			(
				array
				(
					'conditions' => $condition
				)
			);


			// 

			$nbResults = count($rawResults);


			//

			if ($nbResults == 1)
			{
				if 
				(
					$queryResult['action'] == 'C' && 
					(
						$isStrict === true ||
						$isStrict == 1
					)
				)
				{
					$exceptionsResult[] = array
					(
						EXCEPTION_OBJECT_ALREADY_EXISTS,
						array 
						(
							'line' => $queryResult
						)
					);
				}


				// Extract the raw

				$rawResult = array_pop($rawResults);


				// Load the object

				$object->loadFromRawObject($rawResult);
			}
			elseif ($nbResults > 1)
			{
				$exceptionsResult[] = array
				(
					EXCEPTION_OBJECT_TOO_MANY_FOUND,
					array 
					(
						'line' => $queryResult
					)					
				);
			}
			else
			{
				if ($queryResult['action'] == 'U')
				{
					$exceptionsResult[] = array
					(
						EXCEPTION_OBJECT_NOT_FOUND,
						array 
						(
							'line' => $queryResult
						)						
					);
				}		
			}


			try
			{
				// Process the datas

				$this->processObjectFields($object, $queryResult, $objectProperties, $timezone, $exceptionsResult);

						
				// Convert date to time

				switch ($queryResult['action'])
				{
					case 'C':				
					case 'U':

							$object->save($objectProperties);		

							break;

					case 'D':

							$object->delete();

						break;
				}
			}
			catch (\Exception $e)
			{
				// Is there a delete action ?

				if ($queryResult['action'] != 'D')
				{
					// Store data in the stack

					$query = " INSERT INTO `stack_tmp` (`extensionId`,`model`, `value`, `timezone`, `is_complete`) 
									VALUES ('%1','%2','%3', '%4', '0') ";

					$queryVariables = array 
					(
						$object->extensionId,
						$className,
						json_encode($queryResult),
						$timezone
					);

					$databaseDriver->executeQuery($query, $queryVariables);						
				}
				else 
				{
					$exceptionsResult[] = $e;
				}


				continue;
			}
		}


		// Remove the temporary table

		$query =
		"
		DROP TABLE IF EXISTS
		`%1`.`%2_tmp`
		";

		$queryVariables = array
		(
			$object->databaseName,
			$object->viewName
		);

		$object->databaseDriver->executeQuery($query, $queryVariables);


		return $exceptionsResult;
	}		


	/**
	 *
	 */

	private function processObjectFields($object, $queryResult, &$objectProperties, $timezone, &$exceptionsResult)
	{
		// For each result

		foreach ($queryResult as $key => $value)
		{
			// If key = action, continue

			if ($key == 'action')
			{
				continue;
			}


			if (strrpos($key, 'dateOf') !== false)
			{
				$key = str_replace('dateOf', 'timeOf', $key);

				if (empty($value) === false)
				{
					// Convert date to timezone

					$date = app()->getTimeHelper()->convertDateToTimezone($value, null, DATE_W3C, $timezone);


					// Get the timestamps

					$day = substr($date, 8, 2);
					$hour = substr($date, 11, 2);
					$minute = substr($date, 14, 2);
					$month = substr($date, 5, 2);
					$second = substr($date, 17, 2);
					$year = substr($date, 0, 4);

					$value = mktime($hour, $minute, $second, $month, $day, $year);
				}
			}


			// If key is source or id, add the prefix external

			if 
			(
				$key == 'source' ||
				$key == 'id' ||
				$key == 'timeOfCreation'
			)
			{
				$key = 'external' . ucfirst($key);
			}


			// Define properties

			$properties = array_merge($object->staticProperties, $object->localizedProperties);


			// Convert properties into fields

			foreach ($properties as $propertyName => $property)
			{
				// Is it an association ?

				if
				(
					$key == $propertyName && 
					$property['isAssociation'] === true &&
					empty($value) === false
				) 
				{
					// Dependencies

					require_once($property['pathToExtension'] . 'Model/' . $property['className'] . '.php');

					$name = $property['className'];


					// Does the class exists ?

					if (class_exists($name) === false)
					{
						$name = '\\' . str_replace('-', '\\', $property['extensionId']) . '\\' . $name;

					}


					// Instanciate the class

					$objectAssociation = new $name();
				

					// Search the association

					$rawObjects = $objectAssociation->find
					(
						array
						(
							'conditions' => array
							(
								'externalId' => array(EQUAL, $value),
								'externalSource' => array(EQUAL, $objectProperties[$object->objectName . 'ExternalSource'])
							)
						)
					);


					// Count the number of results

					$nbObjects = count($rawObjects);

					if ($nbObjects == 0)
					{
						$exceptionsResult[] = array
						(
							EXCEPTION_OBJECT_NOT_FOUND,
							array 
							(
								'className' => $objectAssociation->className,
								'externalId' => $value,
								'externalSource' => $objectProperties[$object->objectName . 'ExternalSource']
							)
						);
					}
					elseif ($nbObjects >1) 
					{
						$exceptionsResult[] = array(EXCEPTION_OBJECT_TOO_MANY_FOUND);
					}
					else
					{
						// Extract the raw

						$rawObject = array_pop($rawObjects);


						// Load the raw

						$objectAssociation->loadFromRawObject($rawObject);
					}


					// Set the value

					$value = $objectAssociation->getId();
				}
			}


			// Set  null, if no value 

			if (empty($value) === true)
			{
				$value = null;
			}


			// Associate the value to the name of the property

			$objectProperties[$object->objectName . ucfirst($key)] = $value ;

		}
	}


	/**
	 * Format the pattern and the sql type of one property
	 * 
	 * @param array $propertyOptions this array containt all of options about one property
	 */
	
	public function processPropertyType(&$propertyOptions)
	{
		// Extract the type

		$type = json_decode($propertyOptions['type']);


		// Get additional properties

		if (empty($propertyOptions['fieldType']) == true)
		{
			$propertyOptions['fieldType'] = $type->fieldType;
		}

		$propertyOptions['defaultValue'] = $type->defaultValue;
		$propertyOptions['pattern'] = $type->pattern;
		$propertyOptions['sqlType'] = $type->sqlType;
		$propertyOptions['values'] = $type->values;
	}


	/**
	 *
	 */

	private function processStack($nbLastResults = null)
	{
		// Initialie variables

		$exceptions = array();
		$nbFailed = 0;


		// Search the data to process

		$query =  "SELECT `id`, `extensionId`, `model`, `value` , `timezone` 
					FROM `stack_tmp`
					WHERE `is_complete` = '0'
					";


		// Get the database driver

		$databaseDriver = app()->getDatabaseDriver(EXTENSION_APPLICATION);


		// Execute the query

		$queryResults = $databaseDriver->executeQuery($query, array());


		// Get the number of row

		$nbResults = $databaseDriver->getNbRows($queryResults);
		

		// Is there new results to insert?

		if 
		(
			$nbResults == 0 ||
			$nbResults == $nbLastResults
		)
		{
			// Store the failed datas

			while ($queryResult = $databaseDriver->fetchAssociativeArray($queryResults))
			{				
				$exceptions[] = $queryResult;
			}


			return $exceptions;
		}


		// For each row found

		while ($queryResult = $databaseDriver->fetchAssociativeArray($queryResults))
		{		
			// Build object

			$object = $this->buildObject($queryResult['extensionId'], $queryResult['model']);


			// Decode properties

			$result = json_decode($queryResult['value']);

			foreach ($result as $property => $value)
			{
				$results[$property] = $value;
			}


			$objectProperties = array();


			// Process the datas

			$this->processObjectFields($object, $results, $objectProperties, $queryResult['timezone'], $exceptionsResult);


			// Save it

			try
			{					

				$object->save($objectProperties);		


				// Update the data in database

				$queryUpdate = "UPDATE `stack_tmp` 
								SET `is_complete` = 1 
								WHERE `id` = %1 ";

				$queryUpdateVariables = array($queryResult['id']);


				// Execute query

				$databaseDriver->executeQuery($queryUpdate, $queryUpdateVariables);
			}
			catch(\Exception $e)
			{
				$nbFailed = $nbFailed + 1;
			}
		}


		// Is there others treatment to do ?

		if 
		(
			$nbFailed > 0 && 
			$nbFailed != $nbLastResults
		)
		{
			$exceptions[] = $this->processStack($nbFailed);
		}


		return $exceptions;
	}

	
	/**
	 *
	 */

	private function processWhere($object, &$where)
	{
		// Make sure $where is an array

		if
		(
			(is_array($where) === false) ||
			(empty($where) === true)
		)
		{
			$where = array();
		}


		// Add the object table to the FROM

		$where[] = "`" . $object->databaseName . "`.`" . $object->viewName . "`.`timeOfDisabling` IS NULL";
		$where[] =
		"
		(
			`" . $object->databaseName . "`.`" . $object->viewName . "`.`isTemporary` IS NULL OR
			`" . $object->databaseName . "`.`" . $object->viewName . "`.`isTemporary` = ''
		)
		";
	}


	/**
	 * Resets the object, i.e. sets all properties to null
	 */
	
	public function reset(&$object)
	{
		// Reset all file properties

		foreach ($object->fileProperties as &$property)
		{
			// And set the value to NULL
			
			$property['object'] = null;
			$property['value'] = null;
			$property['hasChanged'] = null;			
		}


		// Reset all properties
		
		foreach ($object->staticProperties as &$property)
		{
			// And set the value to NULL
			
			$property['object'] = null;
			$property['value'] = null;
			$property['hasChanged'] = null;
		}
		
		
		// Reset all localized properties
		
		foreach ($object->localizedProperties as &$localizedProperty)
		{
			// And set the value to NULL
			
			$localizedProperty['object'] = null;
			$localizedProperty['value'] = null;
			$localizedProperty['hasChanged'] = null;
		}
		
		
		// Reset all smart properties
		
		foreach ($object->smartProperties as $smartPropertyName => &$smartProperty)
		{
			// And set the value to NULL

			$smartProperty['object'] = null;			
			$smartProperty['value'] = null;
			$smartProperty['hasChanged'] = null;
		}
	}	


	/**
	 * Template function used to update an existing object
	 *
	 * @param	bool		$isLocalized		True to update the _L table, false for the main one
	 * @param	string	$languageIsoCode	Optional, the ISO code of the language in which the object should be saved
	 */
	
	public function updateTemplate(&$object, $isLocalized = false, $languageIsoCode = null)
	{
		// Do we have the right to perform this action on the object?
		
		app()->getSecurityManager()->grantPermissionOnObject($object->className, $object->getId(), ACTION_UPDATE);
		
		
		// Should we work on the localized properties?
		
		if ($isLocalized == true)
		{
			$properties = &$object->localizedProperties;
			$tableName = $object->tableName_L;
			
			$whereClause = 'WHERE languageIsoCode = \'' . $languageIsoCode . '\' ';
			$whereClause .= 'AND ' . $object->idName . ' = \'' . $object->getId() . '\'';
		}
		else
		{
			$properties = &$object->staticProperties;
			$tableName = $object->tableName;

			$whereClause = 'WHERE id = \'' . $object->getId() . '\' ';
		}
		
		
		// Setup the updater

		$object->setSourceOfUpdate($object->extensionId);
		$object->setTimeOfUpdate(time());
		$object->setUserUpdaterId(user()->getId());

		
		// Parse each property
		
		$queryFragments = array();
		$nbPropertiesChanged = 0;
		$queryVariables = array($object->databaseName . '`.`' . $tableName);

		$i = count($queryVariables) + 1;

		foreach ($properties as $propertyName => &$propertyOptions)
		{
			// Check if property has changed. Special mention for localize properties who need to be check for all languages

			if (is_array($propertyOptions['hasChanged']) == true)
			{
				foreach ($propertyOptions['hasChanged'] as $hasChanged)
				{
					if ($hasChanged == true)
					{
						break;
					}
				}

				if ($hasChanged == false)
				{
					continue;
				}
			}
			else
			{
				if ($propertyOptions['hasChanged'] == false)
				{
					continue;
				}
			}
			
			if ($propertyName == 'versionId')
			{
				continue;
			}
			
			
			// Remember value
			
			if (is_null($propertyOptions['value']) == true)
			{
				$queryFragments[] = $propertyName . ' = NULL';
			}
			else
			{
				if ($propertyOptions['isLocalized'] == true)
				{
					if (isset($propertyOptions['value'][$languageIsoCode]) == false)
					{
						continue;	
					}

					$queryFragments[] = '`' . $propertyName . '` = \'%' . $i++ . '\'';
					$queryVariables[] = $propertyOptions['value'][$languageIsoCode];
					$propertyOptions['hasChanged'][$languageIsoCode] = false;
				}
				else
				{
					$queryFragments[] = '`' . $propertyName . '` = \'%' . $i++ . '\'';
					$queryVariables[] = $propertyOptions['value'];
					$propertyOptions['hasChanged'] = false;
				}
			}
			
			
			$nbPropertiesChanged++;
		}
		
			
		// Execute SQL query

		if ($nbPropertiesChanged > 0)
		{
			$query = 'UPDATE `%1` SET ' . implode(', ', $queryFragments) . ' ' . $whereClause;

			$object->databaseDriver->executeQuery($query, $queryVariables);
		}
	}	
}

?>
